<template>
  <div>
    <div class="map-wrap">
      <div id="map" class="map-container"></div>
      <div class="map-controls elevation-6" :style="{ left: controlLeft + 'px', top: controlTop + 'px', right: 'auto' }">
        <div class="subtitle-2 font-weight-bold section-title location-section" @mousedown.prevent="startDragControls">Location</div>
        <v-row dense>
          <v-col cols="12" sm="6">
            <v-text-field v-model.number="lat" type="number" step="0.000001" label="Latitude" dense outlined hide-details />
          </v-col>
          <v-col cols="12" sm="6">
            <v-text-field v-model.number="lng" type="number" step="0.000001" label="Longitude" dense outlined hide-details />
          </v-col>
        </v-row>
        <v-tabs
          v-model="activeTab"
          background-color="transparent"
          color="primary"
          grow
          class="mb-2"
        >
          <v-tab href="#series">Time Series</v-tab>
          <v-tab href="#heatmap">Annual Heatmap</v-tab>
          <v-tab href="#cumulative">Cumulative</v-tab>
        </v-tabs>
        <v-tabs-items v-model="activeTab">
          <v-tab-item value="series">
            <div class="subtitle-2 font-weight-bold section-title time-range">Time Range</div>
            <v-row dense>
              <v-col cols="12" sm="6">
                <v-text-field v-model="startDate" type="date" label="Start Date" dense outlined hide-details />
              </v-col>
              <v-col cols="12" sm="6">
                <v-text-field v-model="endDate" type="date" label="End Date" dense outlined hide-details />
              </v-col>
            </v-row>
            <div class="subtitle-2 font-weight-bold section-title">Actions</div>
            <v-row dense>
              <v-col cols="12">
                <v-btn color="primary" dark block :disabled="posting" @click="handleQuery">
                  <v-progress-circular v-if="posting" indeterminate size="16" width="2" color="white" class="mr-2" />
                  Query
                </v-btn>
              </v-col>
            </v-row>
          </v-tab-item>
          <v-tab-item value="heatmap">
            <div class="subtitle-2 font-weight-bold section-title">Year Selection</div>
            <v-row dense>
              <v-col cols="12" sm="6">
                <v-select
                  v-model="selectedYear"
                  :items="yearOptions"
                  label="Select Year"
                  dense
                  outlined
                  hide-details
                  @change="onYearChange"
                />
              </v-col>
              <v-col cols="12" sm="6">
                <v-select
                  v-model="dataSourceId"
                  :items="dataSourceOptions"
                  label="Data Source"
                  dense
                  outlined
                  hide-details
                />
              </v-col>
              <v-col cols="12">
                <v-btn color="primary" dark block :disabled="loadingHeatmap" @click="fetchYearData">
                  <v-progress-circular v-if="loadingHeatmap" indeterminate size="16" width="2" color="white" class="mr-2" />
                  Generate Heatmap
                </v-btn>
              </v-col>
              <v-col cols="12">
                <v-btn color="secondary" outlined block :disabled="loadingHeatmap" @click="useDemoData">Use Demo Data</v-btn>
              </v-col>
            </v-row>
          </v-tab-item>
          <v-tab-item value="cumulative">
            <div class="subtitle-2 font-weight-bold section-title">Year Selection</div>
            <v-row dense>
              <v-col cols="12" sm="6">
                <v-select
                  v-model="selectedYear"
                  :items="yearOptions"
                  label="Select Year"
                  dense
                  outlined
                  hide-details
                />
              </v-col>
              <v-col cols="12" sm="6">
                <v-select
                  v-model="dataSourceId"
                  :items="dataSourceOptions"
                  label="Data Source"
                  dense
                  outlined
                  hide-details
                />
              </v-col>
              <v-col cols="12">
                <v-switch v-model="normalizeCumulative" label="Normalize to 0–1" dense hide-details inset></v-switch>
              </v-col>
              <v-col cols="12">
                <v-btn color="primary" dark block :disabled="loadingCumulative" @click="fetchCumulativeYearData">
                  <v-progress-circular v-if="loadingCumulative" indeterminate size="16" width="2" color="white" class="mr-2" />
                  Generate Cumulative
                </v-btn>
              </v-col>
            </v-row>
          </v-tab-item>
        </v-tabs-items>
        
      </div>
    </div>
    <div
      v-if="chartDialog"
      class="floating-chart highest-dialog"
      :style="{ left: panelLeft + 'px', top: panelTop + 'px', width: panelWidth, height: panelHeight }"
    >
      <v-card class="chart-card">
        <v-card-title class="font-weight-bold">
          PV Output (Time Series)
          <v-spacer></v-spacer>
          <v-btn icon small @click="closeChart"><v-icon>mdi-close</v-icon></v-btn>
        </v-card-title>
        <v-divider></v-divider>
        <v-card-text class="chart-body">
          <div ref="chartEl" class="chart-el"></div>
        </v-card-text>
      </v-card>
    </div>
    <div
      v-if="heatmapDialog"
      class="floating-heatmap highest-dialog"
      :style="{ left: panelLeft + 'px', top: panelTop + 'px', width: panelWidth, height: panelHeight }"
    >
      <v-card class="chart-card">
        <v-card-title class="font-weight-bold">
          Solar Energy Output Heatmap - {{ selectedYear }}
          <v-spacer></v-spacer>
          <v-btn icon small @click="closeHeatmap"><v-icon>mdi-close</v-icon></v-btn>
        </v-card-title>
        <v-divider></v-divider>
        <v-card-text class="chart-body">
          <div v-if="loadingHeatmap" class="loading-overlay">
            <v-progress-circular indeterminate size="48" color="primary" />
            <div class="mt-2">Loading data...</div>
          </div>
          <div v-else-if="heatmapData.length > 0" class="heatmap-content">
            <div class="heatmap-calendar" :style="{ '--weeks': numWeeks, '--y-axis-width': '56px' }">
              <div class="calendar-year-grid">
                <div v-for="(month, mi) in calendarMonths" :key="'m-'+mi" class="month-card">
                  <div class="month-title">{{ month.label }}</div>
                  <div class="month-grid">
                    <div class="month-weekdays">
                      <div v-for="(w, wi) in weekdayLabels" :key="'wd-'+wi" class="weekday">{{ w }}</div>
                    </div>
                    <div class="month-cells" :style="{ gridTemplateRows: 'repeat(' + month.rows + ', 1fr)' }">
                      <template v-for="r in month.rows">
                        <template v-for="c in 7">
                          <div
                            :key="'mc-' + mi + '-' + r + '-' + c"
                            class="heatmap-cell"
                            :class="{ 'is-empty': month.cells[r - 1][c - 1] == null }"
                            :style="monthCellStyle(month, r - 1, c - 1)"
                            :title="monthCellTitle(month, r - 1, c - 1)"
                          >
                            <div class="cell-value" v-if="month.cells[r - 1][c - 1] != null" :style="cellValueStyle(month.cells[r - 1][c - 1])">
                              {{ month.cells[r - 1][c - 1].toFixed(1) }}
                            </div>
                          </div>
                        </template>
                      </template>
                    </div>
                  </div>
                </div>
              </div>
            </div>
            <div class="heatmap-legend">
              <div class="legend-title">Output Range (kWh)</div>
              <div class="legend-gradient">
                <div class="legend-labels">
                  <span>{{ minDailyOutput.toFixed(1) }}</span>
                  <span>{{ maxDailyOutput.toFixed(1) }}</span>
                </div>
                <div class="gradient-bar"></div>
              </div>
            </div>
          </div>
          <div v-else class="empty-state">
            <v-icon size="48" color="grey lighten-1">mdi-chart-line</v-icon>
            <div class="mt-2 grey--text">No data available</div>
          </div>
        </v-card-text>
      </v-card>
    </div>
    <div
      v-if="cumulativeDialog"
      class="floating-cumulative highest-dialog"
      :style="{ left: panelLeft + 'px', top: panelTop + 'px', width: panelWidth, height: panelHeight }"
    >
      <v-card class="chart-card">
        <v-card-title class="font-weight-bold">
          Cumulative Curve — Day {{ cumulativeDay }} of {{ cumulativeDaysCount }}
          <v-spacer></v-spacer>
          <v-btn small class="mr-2" @click="openCumulative3D">3D View</v-btn>
          <v-btn icon small @click="closeCumulative"><v-icon>mdi-close</v-icon></v-btn>
        </v-card-title>
        <v-divider></v-divider>
        <v-card-text class="chart-body">
          <div class="px-3 pt-2">
            <v-slider
              v-model="cumulativeDay"
              :min="1"
              :max="cumulativeDaysCount"
              :step="1"
              thumb-label
              dense
              hide-details
            />
          </div>
          <div ref="cumulativeEl" class="chart-el"></div>
        </v-card-text>
      </v-card>
    </div>
    <div
      v-if="cumulative3DDialog"
      class="floating-cumulative-3d highest-dialog"
      :style="{ left: panelLeft + 'px', top: panelTop + 'px', width: panelWidth, height: panelHeight }"
    >
      <v-card class="chart-card">
        <v-card-title class="font-weight-bold">
          Cumulative Curves — 3D View ({{ selectedYear }})
          <v-spacer></v-spacer>
          <v-btn small class="mr-2" @click="toggleCumulative3DRotation" :color="cumulative3DAutoRotate ? 'primary' : 'grey'">
            <v-icon left>{{ cumulative3DAutoRotate ? 'mdi-pause' : 'mdi-rotate-3d-variant' }}</v-icon>
            {{ cumulative3DAutoRotate ? 'Stop' : 'Rotate' }}
          </v-btn>
          <v-btn icon small @click="closeCumulative3D"><v-icon>mdi-close</v-icon></v-btn>
        </v-card-title>
        <v-divider></v-divider>
        <v-card-text class="chart-body">
          <div class="px-3 pt-2">
            <v-slider
              v-model="cumulative3DDay"
              :min="1"
              :max="cumulativeDaysCount"
              :step="1"
              thumb-label
              dense
              hide-details
            />
          </div>
          <div ref="cumulative3DEl" class="chart-el chart-el-3d"></div>
        </v-card-text>
      </v-card>
    </div>
  </div>
</template>

<script>
import axios from 'axios'
export default {
  name: 'MapDashboard',
  data() {
    return {
      map: null,
      marker: null,
      // Abu Dhabi approx center
      center: { lat: 24.4539, lng: 54.3773 },
      lat: 24.4539,
      lng: 54.3773,
      activeTab: 'series',
      startDate: '',
      endDate: '',
      posting: false,
      chartDialog: false,
      heatmapDialog: false,
      cumulativeDialog: false,
      cumulative3DDialog: false,
      chartInstance: null,
      cumulativeChart: null,
      cumulative3DChart: null,
      panelLeft: 50,
      panelTop: 20,
      isDragging: false,
      dragStartX: 0,
      dragStartY: 0,
      dragBaseLeft: 0,
      dragBaseTop: 0,
      selectedYear: new Date().getFullYear(),
      yearOptions: [],
      dataSourceId: '1',
      dataSourceOptions: [
        { text: 'Data Source 1', value: '1' },
        { text: 'Data Source 2', value: '2' },
        { text: 'Data Source 3', value: '3' }
      ],
      loadingHeatmap: false,
      heatmapData: [],
      maxDailyOutput: 0,
      minDailyOutput: 0,
      avgDailyOutput: 0,
      totalAnnualOutput: 0
      ,
      // Draggable controls panel (Location)
      controlLeft: 0,
      controlTop: 0,
      controlDragging: false,
      controlDragStartX: 0,
      controlDragStartY: 0,
      controlDragBaseLeft: 0,
      controlDragBaseTop: 0,
      // cumulative
      loadingCumulative: false,
      cumulativeDaysCount: 365,
      cumulativeDay: 1,
      cumulativeSlices: [], // Array< Array<number> > length 24 per day
      cumulativeHourlyOutputs: [],
      normalizeCumulative: true,
      cumulative3DDay: 1,
      cumulative3DHasGL: false,
      cumulative3DAutoRotate: false
    }
  },
  watch: {
    chartDialog(val) {
      if (val) {
        this.$nextTick(this.ensurePanelInBounds)
        window.addEventListener('resize', this.ensurePanelInBounds)
      } else {
        window.removeEventListener('resize', this.ensurePanelInBounds)
      }
    },
    heatmapDialog(val) {
      if (val) {
        this.$nextTick(this.ensurePanelInBounds)
        window.addEventListener('resize', this.ensurePanelInBounds)
      } else {
        window.removeEventListener('resize', this.ensurePanelInBounds)
      }
    }
    ,
    cumulativeDialog(val) {
      if (val) {
        this.$nextTick(this.ensurePanelInBounds)
        window.addEventListener('resize', this.ensurePanelInBounds)
      } else {
        window.removeEventListener('resize', this.ensurePanelInBounds)
      }
    }
    ,
    cumulativeDay() { if (this.cumulativeDialog) this.renderCumulative() }
    ,
    normalizeCumulative() {
      this.recomputeCumulativeFromStored()
      if (this.cumulativeDialog) this.renderCumulative()
    }
    ,
    cumulative3DDay() {
      if (this.cumulative3DDialog) this.updateCumulative3DHighlight()
    }
  },
  computed: {
    panelWidth() {
      const wrap = this.$el && this.$el.querySelector('.map-wrap')
      if (!wrap) return '90vw'
      const wrapRect = wrap.getBoundingClientRect()
      const rightMargin = 10
      const minPanelWidth = 600
      return Math.max(minPanelWidth, wrapRect.width - this.panelLeft - rightMargin) + 'px'
    },
    panelHeight() {
      const wrap = this.$el && this.$el.querySelector('.map-wrap')
      if (!wrap) return '60vh'
      const wrapRect = wrap.getBoundingClientRect()
      const bottomMargin = 10
      const minPanelHeight = 380
      const available = wrapRect.height - this.panelTop - bottomMargin
      return Math.max(minPanelHeight, available) + 'px'
    },
    isEndFuture() {
      // compare end datetime to now
      const endStr = this.composeDateTime(this.endDate, null, true)
      if (!endStr) return false
      const end = new Date(endStr.replace(' ', 'T'))
      return end.getTime() > Date.now()
    },
    historyEnabled() {
      return !this.isEndFuture
    },
    forecastEnabled() {
      return this.isEndFuture
    },
    weekdayLabels() {
      return ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
    },
    calendarMatrix() {
      // Build a 7 x numWeeks matrix where rows are weekday (0=Sun..6=Sat), columns are week index
      const daysInYear = this.isLeapYear(this.selectedYear) ? 366 : 365
      if (!this.heatmapData || this.heatmapData.length === 0) return Array.from({ length: 7 }, () => [])
      const firstDay = new Date(this.selectedYear, 0, 1)
      const sundayFirst = firstDay.getDay() // 0-6 (0=Sun)
      const firstWeekday = (sundayFirst + 6) % 7 // 0=Mon..6=Sun
      const numWeeks = Math.ceil((firstWeekday + daysInYear) / 7)
      const matrix = Array.from({ length: 7 }, () => Array(numWeeks).fill(null))
      for (let dayIdx = 0; dayIdx < daysInYear; dayIdx++) {
        const weekday = (firstWeekday + dayIdx) % 7
        const col = Math.floor((firstWeekday + dayIdx) / 7)
        const value = (this.heatmapData[dayIdx] && this.heatmapData[dayIdx].value) || 0
        matrix[weekday][col] = value
      }
      return matrix
    },
    numWeeks() {
      const daysInYear = this.isLeapYear(this.selectedYear) ? 366 : 365
      const sundayFirst = new Date(this.selectedYear, 0, 1).getDay()
      const firstWeekday = (sundayFirst + 6) % 7
      return Math.ceil((firstWeekday + daysInYear) / 7)
    },
    monthSegments() {
      // Produce month header segments - all on same row, evenly distributed
      const segments = []
      const totalWeeks = this.numWeeks
      const weeksPerMonth = totalWeeks / 12
      for (let m = 0; m < 12; m++) {
        const monthStart = new Date(this.selectedYear, m, 1)
        segments.push({
          label: monthStart.toLocaleString(undefined, { month: 'short' }),
          colStart: Math.floor(m * weeksPerMonth),
          colSpan: Math.ceil(weeksPerMonth)
        })
      }
      return segments
    },
    monthBoundaryCols() {
      // Columns where a new month starts (for vertical separators)
      const cols = []
      const sundayFirst = new Date(this.selectedYear, 0, 1).getDay()
      const firstWeekday = (sundayFirst + 6) % 7
      for (let m = 1; m < 12; m++) {
        const monthStart = new Date(this.selectedYear, m, 1)
        const startOffset = Math.floor((firstWeekday + this.dayOfYearMonday(monthStart)) / 7)
        cols.push(startOffset)
      }
      return cols
    },
    weekNumbers() {
      // ISO-like week numbers (Mon-first). Simple computation for display only.
      const weeks = []
      for (let c = 0; c < this.numWeeks; c++) {
        const mondayDate = this.dateForCell(0, c)
        if (!mondayDate) { weeks.push(''); continue }
        const oneJan = new Date(this.selectedYear, 0, 1)
        const diff = mondayDate.getTime() - oneJan.getTime()
        const day = Math.floor(diff / (24*60*60*1000)) + 1
        const sundayFirst = oneJan.getDay()
        const firstWeekday = (sundayFirst + 6) % 7
        const isoWeek = Math.floor((firstWeekday + day - 1) / 7) + 1
        weeks.push(isoWeek)
      }
      return weeks
    },
    calendarMonths() {
      const months = []
      const year = this.selectedYear
      const monthNames = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
      for (let m = 0; m < 12; m++) {
        const first = new Date(year, m, 1)
        const label = monthNames[m]
        const daysInMonth = new Date(year, m + 1, 0).getDate()
        const sundayFirst = first.getDay()
        const firstWeekday = (sundayFirst + 6) % 7 // 0=Mon..6=Sun
        const totalCells = firstWeekday + daysInMonth
        const rows = Math.ceil(totalCells / 7)
        const cells = Array.from({ length: rows }, () => Array(7).fill(null))
        for (let d = 1; d <= daysInMonth; d++) {
          const idx = firstWeekday + d - 1
          const r = Math.floor(idx / 7)
          const c = idx % 7
          const date = new Date(year, m, d)
          const doy = this.dayOfYearOneBased(date)
          const value = (this.heatmapData[doy - 1] && this.heatmapData[doy - 1].value) || 0
          cells[r][c] = value
        }
        months.push({
          monthIndex: m,
          label,
          rows,
          firstWeekday,
          cells
        })
      }
      return months
    }
  },
  created() {
    const pad2 = n => String(n).padStart(2, '0')
    const fmt = d => `${d.getFullYear()}-${pad2(d.getMonth() + 1)}-${pad2(d.getDate())}`
    const now = new Date()
    const year = now.getFullYear()
    const start = new Date(year, 4, 1) // May 1 (month is 0-based)
    const endExclusive = new Date(year, 4, 11) // May 11 00:00 to cover up to May 10
    this.startDate = fmt(start)
    this.endDate = fmt(endExclusive)
    this.generateYearOptions()
  },
  mounted() {
    this.initMap()
    this.$nextTick(() => {
      this.positionControlsDefault()
      window.addEventListener('resize', this.ensureControlsInBounds)
    })
  },
  beforeDestroy() {
    window.removeEventListener('resize', this.ensureControlsInBounds)
    window.removeEventListener('mousemove', this.onDragControls)
    window.removeEventListener('mouseup', this.endDragControls)
    window.removeEventListener('resize', this.onWindowResizeCumulative)
  },
  methods: {
    initMap() {
      // Use Leaflet (OpenStreetMap) without external API keys
      const L = window.L
      if (!L) {
        console.error('Leaflet not loaded')
        return
      }
      this.map = L.map('map').setView([this.center.lat, this.center.lng], 11)
      L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
        maxZoom: 19,
        attribution: '&copy; OpenStreetMap'
      }).addTo(this.map)
      this.map.on('click', (e) => {
        const { lat, lng } = e.latlng
        this.lat = Number(lat.toFixed(6))
        this.lng = Number(lng.toFixed(6))
        this.updateMarker(lat, lng)
      })
      this.updateMarker(this.center.lat, this.center.lng)
    },
    updateMarker(lat, lng) {
      const L = window.L
      if (this.marker) this.marker.remove()
      this.marker = L.marker([lat, lng]).addTo(this.map)
      this.map.setView([lat, lng])
    },
    async placeMarker() {
      if (Number.isFinite(this.lat) && Number.isFinite(this.lng)) {
        this.updateMarker(this.lat, this.lng)
      }
      return this.postQuery('1')
    },
    async postQuery(dataSourceId) {
      const startDateTime = this.composeDateTime(this.startDate, null, false)
      const endDateTime = this.composeDateTime(this.endDate, null, true)
      if (!startDateTime || !endDateTime) {
        // eslint-disable-next-line no-alert
        alert('Please select Start Date/Time and End Date/Time')
        return
      }
      const apiBase = process.env.VUE_APP_API_BASE || 'http://localhost:8080/api'
      const url = `${apiBase}/proxy/solar-energy`
      const body = {
        lat: this.lat,
        lng: this.lng,
        start_datetime: startDateTime,
        end_datetime: endDateTime,
        timezone: 4,
        data_source_id: String(dataSourceId),
        angle: 45,
        direction: 180,
        rated_power: 200,
        land_form: 'urban',
        temperature_correction_factor: -0.0046,
        module_efficient: 0.13,
        auto_follow_sun: 'stationary',
        reduce_factor: 0.9
      }
      try {
        this.posting = true
        const res = await axios.post(url, body)
        const data = res && (res.data || {})
        const result = data.result || {}
        if (Array.isArray(result.time) && Array.isArray(result.output) && result.output.length > 0) {
          this.showChart(result)
        } else {
          // eslint-disable-next-line no-alert
          alert('No data returned')
        }
      } catch (e) {
        // eslint-disable-next-line no-console
        console.error('Request failed', e)
        // eslint-disable-next-line no-alert
        alert('Request failed')
      } finally {
        this.posting = false
      }
    },
    handleQuery() {
      this.heatmapDialog = false
      this.placeMarker()
    },
    showChart(result) {
      this.chartDialog = true
      this.panelLeft = 50
      this.panelTop = 20
      this.$nextTick(() => {
        const el = this.$refs.chartEl
        if (!el) return
        const chart = window.echarts.init(el)
        const times = result.time || []
        const seriesData = (result.output && result.output[0]) || []
        const dataZoom = []
        if (times.length > 60) {
          dataZoom.push(
            { type: 'slider', start: 0, end: 20, height: 24, bottom: 10 },
            { type: 'inside' }
          )
        }
        chart.setOption({
          title: { text: 'PV Output', left: 'center', textStyle: { fontSize: 14 } },
          tooltip: { trigger: 'axis' },
          dataZoom,
          grid: { left: 12, right: 10, top: 24, bottom: 90 },
          xAxis: { 
            type: 'category', 
            data: times, 
            axisLabel: { 
              rotate: 45,
              formatter: function(value) {
                // Truncate to minute (assumes value has date and time)
                // Supports 'YYYY-MM-DD HH:mm:ss' or ISO 'YYYY-MM-DDTHH:mm:ss'
                if (!value) return value
                var v = String(value)
                // Normalize separator to space
                v = v.replace('T', ' ')
                // Remove milliseconds and seconds
                var idx = v.indexOf(' ')
                if (idx > 0) {
                  var timePart = v.slice(idx + 1)
                  var parts = timePart.split(':')
                  if (parts.length >= 2) {
                    return v.slice(0, idx + 1) + parts[0].padStart(2,'0') + ':' + parts[1].padStart(2,'0')
                  }
                }
                // If there is only time without date
                var tparts = v.split(':')
                if (tparts.length >= 2) {
                  return tparts[0].padStart(2,'0') + ':' + tparts[1].padStart(2,'0')
                }
                return v
              }
            }
          },
          yAxis: { type: 'value', name: 'kW', axisLine: { show: true } },
          series: [{ type: 'line', name: 'Output', data: seriesData, smooth: true, showSymbol: false }]
        })
        this.chartInstance = chart
        // Ensure responsive resize
        window.addEventListener('resize', this.onWindowResize)
      })
    },
    onWindowResize() {
      if (this.chartInstance && this.chartInstance.resize) {
        this.chartInstance.resize()
      }
    },
    closeChart() {
      if (this.chartInstance && this.chartInstance.dispose) {
        try { this.chartInstance.dispose() } catch (e) {
          // eslint-disable-next-line no-console
          console.error('Dispose chart failed', e)
        }
      }
      this.chartInstance = null
      this.chartDialog = false
      window.removeEventListener('resize', this.onWindowResize)
    },
    // ----- Cumulative -----
    async fetchCumulativeYearData() {
      if (!this.lat || !this.lng) return
      this.loadingCumulative = true
      this.cumulativeDialog = false
      try {
        const startDate = `${this.selectedYear}-01-01`
        const endDate = `${this.selectedYear}-12-31`
        const apiBase = process.env.VUE_APP_API_BASE || 'http://localhost:8080/api'
        const url = `${apiBase}/proxy/solar-energy`
        const body = {
          lat: this.lat,
          lng: this.lng,
          start_datetime: `${startDate} 00:00`,
          end_datetime: `${endDate} 23:59`,
          timezone: 4,
          data_source_id: String(this.dataSourceId),
          angle: 45,
          direction: 180,
          rated_power: 200,
          land_form: 'urban',
          temperature_correction_factor: -0.0046,
          module_efficient: 0.13,
          auto_follow_sun: 'stationary',
          reduce_factor: 0.9
        }
        const res = await axios.post(url, body)
        const data = res && (res.data || {})
        const result = data.result || {}
        if (Array.isArray(result.output) && result.output[0]) {
          // 确保我们获取的是正确的数据数组
          const outputData = Array.isArray(result.output[0]) ? result.output[0] : result.output
          this.prepareCumulativeFromOutputs(outputData)
        } else {
          this.prepareCumulativeDemo()
        }
      } catch (e) {
        this.prepareCumulativeDemo()
      } finally {
        this.loadingCumulative = false
        this.cumulativeDialog = true
        this.panelLeft = 50
        this.panelTop = 20
        this.$nextTick(() => { this.ensurePanelInBounds(); this.renderCumulative(); })
      }
    },
    prepareCumulativeFromOutputs(hourlyOutputs) {
      this.cumulativeHourlyOutputs = hourlyOutputs.slice()
      const hoursPerDay = 24
      // 确保数据长度是24的整数倍，如果不是则截断到最近的完整天数
      const totalHours = Math.floor(hourlyOutputs.length / hoursPerDay) * hoursPerDay
      const days = totalHours / hoursPerDay
      
      // 限制天数不超过实际年份的天数
      const maxDaysInYear = this.isLeapYear(this.selectedYear) ? 366 : 365
      const actualDays = Math.min(days, maxDaysInYear)
      
      // 调试信息
      console.log(`Cumulative data: ${hourlyOutputs.length} hours, ${days} days, limited to ${actualDays} days`)
      
      this.cumulativeDaysCount = actualDays
      this.cumulativeDay = 1
      this.cumulativeSlices = []
      
      // 只处理实际年份的天数
      for (let d = 0; d < actualDays; d++) {
        const slice = hourlyOutputs.slice(d * hoursPerDay, d * hoursPerDay + hoursPerDay)
        const cum = []
        let sum = 0
        const denom = this.normalizeCumulative ? Math.max(1e-6, slice.reduce((s,v)=>s+(v||0),0)) : 1
        for (let i = 0; i < 24; i++) {
          const v = slice[i] || 0
          sum += v
          cum.push(this.normalizeCumulative ? sum / denom : sum)
        }
        this.cumulativeSlices.push(cum)
      }
    },
    prepareCumulativeDemo() {
      const days = this.isLeapYear(this.selectedYear) ? 366 : 365
      this.cumulativeDaysCount = days
      this.cumulativeDay = 1
      this.cumulativeSlices = []
      this.cumulativeHourlyOutputs = []
      for (let d = 0; d < days; d++) {
        const base = 0.5 + 0.5 * Math.sin((d - 172) * 2 * Math.PI / 365)
        const slice = Array.from({ length: 24 }, (_, h) => Math.max(0, base * Math.max(0, Math.sin((h - 6) * Math.PI / 12))))
        this.cumulativeHourlyOutputs.push(...slice)
        let sum = 0
        const denom = this.normalizeCumulative ? slice.reduce((s,v)=>s+v,0) || 1 : 1
        const cum = slice.map(v => { sum += v; return this.normalizeCumulative ? sum/denom : sum })
        this.cumulativeSlices.push(cum)
      }
    },
    recomputeCumulativeFromStored() {
      if (!this.cumulativeHourlyOutputs || this.cumulativeHourlyOutputs.length === 0) return
      this.prepareCumulativeFromOutputs(this.cumulativeHourlyOutputs)
    },
    renderCumulative() {
      const el = this.$refs.cumulativeEl
      if (!el) return
      const chart = this.cumulativeChart || window.echarts.init(el)
      this.cumulativeChart = chart
      const hours = Array.from({ length: 24 }, (_, i) => i)
      const series = [{ type: 'line', data: this.cumulativeSlices[this.cumulativeDay - 1] || [], smooth: true, showSymbol: false }]
      chart.setOption({
        title: { text: this.normalizeCumulative ? 'Cumulative (0–1)' : 'Cumulative', left: 'center', textStyle: { fontSize: 14 } },
        tooltip: { trigger: 'axis' },
        grid: { left: 40, right: 20, top: 28, bottom: 40 },
        xAxis: { type: 'category', data: hours, name: 'Hour' },
        yAxis: { type: 'value', min: 0, max: this.normalizeCumulative ? 1 : null },
        series
      })
      window.addEventListener('resize', this.onWindowResizeCumulative)
    },
    onWindowResizeCumulative() {
      if (this.cumulativeChart && this.cumulativeChart.resize) this.cumulativeChart.resize()
    },
    closeCumulative() {
      if (this.cumulativeChart && this.cumulativeChart.dispose) {
        try { this.cumulativeChart.dispose() } catch (e) { // eslint-disable-next-line no-console
          console.error('Dispose cumulative chart failed', e) }
      }
      this.cumulativeChart = null
      this.cumulativeDialog = false
      window.removeEventListener('resize', this.onWindowResizeCumulative)
    },
    openCumulative3D() { this.cumulative3DDay = this.cumulativeDay; this.cumulative3DDialog = true; this.$nextTick(() => { this.ensurePanelInBounds(); this.renderCumulative3D() }) },
    closeCumulative3D() {
      if (this.cumulative3DChart && this.cumulative3DChart.dispose) { try { this.cumulative3DChart.dispose() } catch (e) { // eslint-disable-next-line no-console
        console.error('Dispose cumulative 3D chart failed', e) } }
      this.cumulative3DChart = null
      this.cumulative3DDialog = false
    },
    renderCumulative3D() {
      const el = this.$refs.cumulative3DEl
      if (!el) return
      const echarts = window.echarts
      // Generate color palette for each day
      const generateDayColor = (dayIndex) => {
        const hue = (dayIndex * 137.5) % 360 // Golden angle for good distribution
        const saturation = 70 + (dayIndex % 30) // 70-100%
        const lightness = 45 + (dayIndex % 20) // 45-65%
        return `hsl(${hue}, ${saturation}%, ${lightness}%)`
      }
      const renderPseudo3D = () => {
        if (!echarts) return
        const chart = echarts.init(el)
        this.cumulative3DChart = chart
        const series = []
        for (let day = 0; day < this.cumulativeDaysCount; day++) {
          const startIndex = day * 24
          const dayData = []
          for (let hour = 0; hour < 24; hour++) {
            const value = this.cumulativeHourlyOutputs[startIndex + hour] || 0
            dayData.push([hour, value + day * 0.1])
          }
          series.push({
            type: 'line',
            data: dayData,
            showSymbol: false,
            lineStyle: { width: 1, color: generateDayColor(day) }
          })
        }
        chart.setOption({
          title: { text: 'Hourly Values (pseudo-3D)', left: 'center', textStyle: { fontSize: 14 } },
          grid: { left: 40, right: 10, top: '5%', bottom: '5%' },
          xAxis: { type: 'value', name: 'Hour', min: 0, max: 23 },
          yAxis: { type: 'value', name: 'Hourly Value' },
          series
        })
        this.cumulative3DHasGL = false
      }
      if (!echarts) return renderPseudo3D()
      try {
        // Try real 3D first; if echarts-gl isn't present, this may throw
        const chart = echarts.init(el)
        this.cumulative3DChart = chart
        // Create multiple line3D series for each day with different colors
        const series = []
        for (let day = 0; day < this.cumulativeDaysCount; day++) {
          // Get the 24 hours of data for this day from cumulativeHourlyOutputs
          const startIndex = day * 24
          const dayData = []
          for (let hour = 0; hour < 24; hour++) {
            const value = this.cumulativeHourlyOutputs[startIndex + hour] || 0
            dayData.push([hour, day + 1, value])
          }
          const dayColor = generateDayColor(day)
          series.push({
            type: 'line3D',
            data: dayData,
            lineStyle: { 
              width: day + 1 === this.cumulative3DDay ? 4 : 2, 
              color: dayColor,
              opacity: day + 1 === this.cumulative3DDay ? 1 : 0.7
            },
            silent: false
          })
        }
        chart.setOption({
          tooltip: {
            formatter: function(params) {
              if (params.seriesType === 'line3D') {
                const actualDay = Math.floor(params.data[1])
                return `Day ${actualDay}<br/>Hour ${params.data[0]}<br/>Hourly Value: ${params.data[2].toFixed(3)}`
              }
              return params.name
            }
          },
          grid3D: {
            boxWidth: 120,
            boxDepth: 220,
            top: '2%',
            bottom: '28%',
            viewControl: { 
              autoRotate: this.cumulative3DAutoRotate, 
              damping: 0.6, 
              alpha: 20, 
              beta: 40,
              autoRotateSpeed: 10,
              rotateSensitivity: 1,
              zoomSensitivity: 1
            }
          },
          xAxis3D: { type: 'value', name: 'Hour', min: 0, max: 23 },
          yAxis3D: { 
            type: 'value', 
            name: 'Day', 
            min: 1, 
            max: Math.min(this.cumulativeDaysCount, this.isLeapYear(this.selectedYear) ? 366 : 365),
            interval: Math.ceil(Math.min(this.cumulativeDaysCount, this.isLeapYear(this.selectedYear) ? 366 : 365) / 10),
            axisLabel: { show: true },
            axisTick: { show: true },
            splitLine: { show: false }
          },
          zAxis3D: { type: 'value', name: 'Hourly Value' },
          series
        })
        this.cumulative3DHasGL = true
      } catch (e) {
        renderPseudo3D()
      }
    },
    updateCumulative3DHighlight() {
      const echarts = window.echarts
      if (!this.cumulative3DChart || !echarts || !this.cumulative3DHasGL) {
        // Fallback re-render for pseudo-3D
        this.renderCumulative3D()
        return
      }
      // Update line styles to highlight selected day
      const series = []
      for (let day = 0; day < this.cumulativeDaysCount; day++) {
        // Get the 24 hours of data for this day from cumulativeHourlyOutputs
        const startIndex = day * 24
        const dayData = []
        for (let hour = 0; hour < 24; hour++) {
          const value = this.cumulativeHourlyOutputs[startIndex + hour] || 0
          dayData.push([hour, day + 1, value])
        }
        const dayColor = this.generateDayColor(day)
        series.push({
          type: 'line3D',
          data: dayData,
          lineStyle: { 
            width: day + 1 === this.cumulative3DDay ? 4 : 2, 
            color: dayColor,
            opacity: day + 1 === this.cumulative3DDay ? 1 : 0.7
          },
          silent: false
        })
      }
      this.cumulative3DChart.setOption({ series })
    },
    generateDayColor(dayIndex) {
      const hue = (dayIndex * 137.5) % 360 // Golden angle for good distribution
      const saturation = 70 + (dayIndex % 30) // 70-100%
      const lightness = 45 + (dayIndex % 20) // 45-65%
      return `hsl(${hue}, ${saturation}%, ${lightness}%)`
    },
    toggleCumulative3DRotation() {
      this.cumulative3DAutoRotate = !this.cumulative3DAutoRotate
      if (this.cumulative3DChart && this.cumulative3DHasGL) {
        this.cumulative3DChart.setOption({
          grid3D: {
            viewControl: { autoRotate: this.cumulative3DAutoRotate }
          }
        })
      }
    },
    // ----- Draggable controls (Location panel) -----
    positionControlsDefault() {
      const wrap = this.$el && this.$el.querySelector('.map-wrap')
      const panel = this.$el && this.$el.querySelector('.map-controls')
      if (!wrap || !panel) return
      const wrapRect = wrap.getBoundingClientRect()
      const panelRect = panel.getBoundingClientRect()
      const rightMargin = 16
      const topMargin = 16
      const left = Math.max(0, wrapRect.width - panelRect.width - rightMargin)
      const top = Math.max(0, topMargin)
      this.controlLeft = left
      this.controlTop = top
    },
    ensureControlsInBounds() {
      const wrap = this.$el && this.$el.querySelector('.map-wrap')
      const panel = this.$el && this.$el.querySelector('.map-controls')
      if (!wrap || !panel) return
      const wrapRect = wrap.getBoundingClientRect()
      const panelRect = panel.getBoundingClientRect()
      const maxLeft = Math.max(0, wrapRect.width - panelRect.width)
      const maxTop = Math.max(0, wrapRect.height - panelRect.height)
      this.controlLeft = Math.min(Math.max(this.controlLeft, 0), maxLeft)
      this.controlTop = Math.min(Math.max(this.controlTop, 0), maxTop)
    },
    startDragControls(e) {
      const panel = this.$el && this.$el.querySelector('.map-controls')
      if (!panel) return
      this.controlDragging = true
      this.controlDragStartX = e.clientX
      this.controlDragStartY = e.clientY
      this.controlDragBaseLeft = this.controlLeft
      this.controlDragBaseTop = this.controlTop
      window.addEventListener('mousemove', this.onDragControls)
      window.addEventListener('mouseup', this.endDragControls)
    },
    onDragControls(e) {
      if (!this.controlDragging) return
      const dx = e.clientX - this.controlDragStartX
      const dy = e.clientY - this.controlDragStartY
      this.controlLeft = this.controlDragBaseLeft + dx
      this.controlTop = this.controlDragBaseTop + dy
      this.ensureControlsInBounds()
    },
    endDragControls() {
      if (!this.controlDragging) return
      this.controlDragging = false
      window.removeEventListener('mousemove', this.onDragControls)
      window.removeEventListener('mouseup', this.endDragControls)
      this.ensureControlsInBounds()
    },
    ensurePanelInBounds() {
      const wrap = this.$el && this.$el.querySelector('.map-wrap')
      const panel = this.$el && this.$el.querySelector('.floating-chart, .floating-heatmap, .floating-cumulative, .floating-cumulative-3d')
      if (!wrap || !panel) return
      const wrapRect = wrap.getBoundingClientRect()
      const panelRect = panel.getBoundingClientRect()
      const rightMargin = 10
      const minPanelWidth = 600
      const maxLeft = Math.max(0, wrapRect.width - rightMargin - minPanelWidth)
      const maxTop = Math.max(0, wrapRect.height - panelRect.height)
      this.panelLeft = Math.min(Math.max(this.panelLeft, 0), maxLeft)
      this.panelTop = Math.min(Math.max(this.panelTop, 0), maxTop)
    },
    composeDateTime(d, t, isEnd) {
      if (!d) return ''
      // d expected as YYYY-MM-DD. time not needed by UI anymore.
      const date = String(d)
      if (isEnd) {
        // end of day: 23:59 to cover full day
        return `${date} 23:59`
      }
      // start of day
      return `${date} 00:00`
    },
    dayOfYearMonday(date) {
      // Day index with Monday-based week alignment
      const start = new Date(date.getFullYear(), 0, 1)
      const diff = date.getTime() - start.getTime()
      const day = Math.floor(diff / (24 * 60 * 60 * 1000))
      return day
    },
    generateYearOptions() {
      const currentYear = new Date().getFullYear()
      this.yearOptions = []
      for (let year = currentYear - 5; year <= currentYear + 1; year++) {
        this.yearOptions.push(year)
      }
    },
    onYearChange() {
      this.heatmapData = []
      this.resetHeatmapStats()
    },
    resetHeatmapStats() {
      this.maxDailyOutput = 0
      this.minDailyOutput = 0
      this.avgDailyOutput = 0
      this.totalAnnualOutput = 0
    },
    async fetchYearData() {
      if (!this.lat || !this.lng) {
        // eslint-disable-next-line no-console
        console.error('Please enter valid coordinates')
        return
      }
      this.chartDialog = false
      this.loadingHeatmap = true
      this.heatmapData = []
      this.resetHeatmapStats()
      try {
        const startDate = `${this.selectedYear}-01-01`
        const endDate = `${this.selectedYear}-12-31`
        const apiBase = process.env.VUE_APP_API_BASE || 'http://localhost:8080/api'
        const url = `${apiBase}/proxy/solar-energy`
        const body = {
          lat: this.lat,
          lng: this.lng,
          start_datetime: `${startDate} 00:00`,
          end_datetime: `${endDate} 23:59`,
          timezone: 4,
          data_source_id: String(this.dataSourceId),
          angle: 45,
          direction: 180,
          rated_power: 200,
          land_form: 'urban',
          temperature_correction_factor: -0.0046,
          module_efficient: 0.13,
          auto_follow_sun: 'stationary',
          reduce_factor: 0.9
        }
        const res = await axios.post(url, body)
        const data = res && (res.data || {})
        const result = data.result || {}
        if (Array.isArray(result.time) && Array.isArray(result.output) && result.output.length > 0) {
          this.processYearData(result)
        } else {
          // eslint-disable-next-line no-console
          console.error('No data returned for the selected year')
          this.useDemoData()
        }
      } catch (e) {
        // eslint-disable-next-line no-console
        console.error('Failed to fetch year data', e)
        this.useDemoData()
      } finally {
        this.loadingHeatmap = false
        this.heatmapDialog = true
        this.panelLeft = 50
        this.panelTop = 20
        this.$nextTick(this.ensurePanelInBounds)
      }
    },
    useDemoData() {
      const daysInYear = this.isLeapYear(this.selectedYear) ? 366 : 365
      const demoData = []
      for (let day = 1; day <= daysInYear; day++) {
        const dayOfYear = day
        const seasonalFactor = 0.5 + 0.5 * Math.sin((dayOfYear - 172) * 2 * Math.PI / 365)
        const randomFactor = 0.8 + 0.4 * Math.random()
        const dailyOutput = 100 * seasonalFactor * randomFactor
        demoData.push({ day, value: Math.max(0, dailyOutput) })
      }
      this.heatmapData = demoData
      this.calculateHeatmapStats()
      this.heatmapDialog = true
      this.panelLeft = 50
      this.panelTop = 20
      this.$nextTick(this.ensurePanelInBounds)
    },
    processYearData(result) {
      const outputs = (result.output && result.output[0]) || []
      const daysInYear = this.isLeapYear(this.selectedYear) ? 366 : 365
      const dailyData = []
      for (let day = 0; day < daysInYear; day++) {
        const dayStartIndex = day * 24
        const dayEndIndex = Math.min(dayStartIndex + 24, outputs.length)
        if (dayStartIndex < outputs.length) {
          const dayOutputs = outputs.slice(dayStartIndex, dayEndIndex)
          const dailyTotal = dayOutputs.reduce((sum, output) => sum + (output || 0), 0)
          dailyData.push({ day: day + 1, value: dailyTotal })
        } else {
          dailyData.push({ day: day + 1, value: 0 })
        }
      }
      this.heatmapData = dailyData
      this.calculateHeatmapStats()
    },
    isLeapYear(year) {
      return (year % 4 === 0 && year % 100 !== 0) || (year % 400 === 0)
    },
    calculateHeatmapStats() {
      if (this.heatmapData.length === 0) return
      const values = this.heatmapData.map(d => d.value).filter(v => v > 0)
      if (values.length > 0) {
        this.maxDailyOutput = Math.max(...values)
        this.minDailyOutput = Math.min(...values)
        this.avgDailyOutput = values.reduce((sum, val) => sum + val, 0) / values.length
        this.totalAnnualOutput = this.heatmapData.reduce((sum, d) => sum + d.value, 0)
      }
    },
    cellStyle(row, col) {
      const value = this.calendarMatrix[row] && this.calendarMatrix[row][col]
      const hasValue = value != null
      return {
        backgroundColor: hasValue ? this.getHeatmapColor(value) : '#fafafa',
        opacity: hasValue && value > 0 ? 0.85 : 0.35
      }
    },
    cellTitle(row, col) {
      const value = this.calendarMatrix[row] && this.calendarMatrix[row][col]
      if (value == null) return 'No data'
      // compute date label
      const firstDay = new Date(this.selectedYear, 0, 1)
      const firstWeekday = (firstDay.getDay() + 6) % 7
      const dayIndex = col * 7 + row - firstWeekday
      const date = new Date(this.selectedYear, 0, 1 + Math.max(0, dayIndex))
      const monthNames = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
      const label = `${monthNames[date.getMonth()]} ${date.getDate()}`
      return `${label}: ${value.toFixed(2)} kWh`
    },
    dateForCell(row, col) {
      const firstDay = new Date(this.selectedYear, 0, 1)
      const firstWeekday = (firstDay.getDay() + 6) % 7
      const dayIndex = col * 7 + row - firstWeekday
      if (dayIndex < 0) return null
      return new Date(this.selectedYear, 0, 1 + dayIndex)
    },
    getHeatmapColor(value) {
      if (value <= 0) return '#f5f5f5'
      const maxValue = this.maxDailyOutput || 1
      const ratio = Math.min(value / maxValue, 1)
      if (ratio < 0.25) {
        const blueRatio = ratio * 4
        const blue = Math.round(255 * (1 - blueRatio))
        const green = Math.round(255 * blueRatio)
        return `rgb(0, ${green}, ${blue})`
      } else if (ratio < 0.5) {
        const greenRatio = (ratio - 0.25) * 4
        const red = Math.round(255 * greenRatio)
        const green = 255
        return `rgb(${red}, ${green}, 0)`
      } else if (ratio < 0.75) {
        const orangeRatio = (ratio - 0.5) * 4
        const red = 255
        const green = Math.round(255 * (1 - orangeRatio * 0.5))
        return `rgb(${red}, ${green}, 0)`
      } else {
        const redRatio = (ratio - 0.75) * 4
        const red = 255
        const green = Math.round(255 * (0.5 - redRatio * 0.5))
        return `rgb(${red}, ${green}, 0)`
      }
    },
    parseRgb(colorStr) {
      // expects formats like rgb(r, g, b) or #rrggbb
      if (!colorStr) return [255, 255, 255]
      if (colorStr.startsWith('rgb')) {
        const parts = colorStr.replace(/rgba?\(|\)/g, '').split(',').map(s => parseFloat(s.trim()))
        return [parts[0] || 255, parts[1] || 255, parts[2] || 255]
      }
      if (colorStr[0] === '#') {
        const hex = colorStr.slice(1)
        const r = parseInt(hex.slice(0, 2), 16)
        const g = parseInt(hex.slice(2, 4), 16)
        const b = parseInt(hex.slice(4, 6), 16)
        return [r, g, b]
      }
      return [255, 255, 255]
    },
    cellValueStyle(value) {
      if (value == null) return {}
      const bg = this.getHeatmapColor(value)
      const [r, g, b] = this.parseRgb(bg)
      // relative luminance per WCAG
      const srgb = [r / 255, g / 255, b / 255].map(c => (c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4)))
      const luminance = 0.2126 * srgb[0] + 0.7152 * srgb[1] + 0.0722 * srgb[2]
      const color = luminance < 0.5 ? '#ffffff' : '#111111'
      const textShadow = luminance < 0.5 ? '0 0 2px rgba(0,0,0,0.5)' : '0 0 2px rgba(255,255,255,0.85)'
      return { color, textShadow }
    },
    monthCellStyle(month, row, col) {
      const value = month.cells[row] && month.cells[row][col]
      const hasValue = value != null
      return {
        backgroundColor: hasValue ? this.getHeatmapColor(value) : '#fafafa',
        opacity: hasValue && value > 0 ? 0.85 : 0.35
      }
    },
    monthCellTitle(month, row, col) {
      const value = month.cells[row] && month.cells[row][col]
      if (value == null) return 'No data'
      const dayOfMonth = row * 7 + col - month.firstWeekday + 1
      const date = new Date(this.selectedYear, month.monthIndex, dayOfMonth)
      const monthNames = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
      const label = `${monthNames[date.getMonth()]} ${date.getDate()}`
      return `${label}: ${value.toFixed(2)} kWh`
    },
    dayOfYearOneBased(date) {
      const start = new Date(date.getFullYear(), 0, 1)
      const diff = date.getTime() - start.getTime()
      return Math.floor(diff / (24 * 60 * 60 * 1000)) + 1
    },
    closeHeatmap() {
      this.heatmapDialog = false
    }
  }
}
</script>

<style scoped>
.map-wrap { position: relative; }
.map-container {
  width: 100%;
  height: calc(100vh - 160px);
  border: 1px solid #e0e0e0;
  border-radius: 8px;
  overflow: hidden;
}
.map-controls {
  position: absolute;
  /* left/top set via inline style for dragging */
  background: #fff;
  border-radius: 10px;
  padding: 10px 12px;
  z-index: 4001; /* above dialog overlay to keep Query visible */
  box-shadow: 0 6px 20px rgba(0,0,0,0.15);
  width: 360px;
}
.map-controls .v-col { padding-top: 2px !important; padding-bottom: 2px !important; }
.map-controls .v-input { margin-bottom: 6px !important; }
.map-controls .subtitle-2 { margin: 2px 0 2px 0 !important; line-height: 1.1; }
.map-controls .section-title { margin-top: 2px !important; cursor: move; user-select: none; }
.map-controls .section-title + .v-row { margin-top: 2px; }
.map-controls .time-range { margin-bottom: 5px !important; }
.map-controls .location-section { margin-bottom: 5px !important; }
.map-controls .caption { margin-top: 4px; }

/* Ensure floating panel stacks above map controls and map */
.floating-chart { position: absolute; z-index: 5002; }
.floating-heatmap { position: absolute; z-index: 5002; }
.floating-cumulative { position: absolute; z-index: 5002; }
.floating-cumulative-3d { position: absolute; z-index: 5002; }
/* drag-handle removed: dragging disabled */

/* Chart dialog styles */
.chart-dialog { width: 90vw; }
.highest-dialog { z-index: 5000 !important; }
.chart-card { height: calc(100% - 0px); width: auto; max-width: 90vw; display: flex; flex-direction: column; }
.chart-body { flex: 1; padding: 0 !important; overflow: auto; }
.chart-el { width: 100%; height: 100%; }
/* Taller 3D container to allow scroll inside dialog if needed */
.chart-el-3d { min-height: 560px; }

/* Heatmap styles */
.heatmap-content { max-width: 1200px; margin: 0 auto; }
.heatmap-calendar { display: grid; grid-template-rows: auto 1fr; gap: 8px; }
.calendar-year-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 12px;
}
.month-card {
  border: 1px solid #eee;
  border-radius: 8px;
  padding: 8px;
  background: #fff;
  box-shadow: 0 2px 6px rgba(0,0,0,0.06);
}
.month-title {
  font-weight: 700;
  font-size: 13px;
  color: #333;
  text-align: center;
  margin-bottom: 6px;
}
.month-grid {
  display: grid;
  grid-template-columns: 24px 1fr;
  gap: 6px;
}
.month-weekdays {
  display: grid;
  grid-template-rows: repeat(7, 1fr);
}
.month-weekdays .weekday {
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 10px;
  color: #666;
  font-weight: 500;
}
.month-cells {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 2px;
}
.heatmap-cell {
  aspect-ratio: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 2px;
  cursor: pointer;
  transition: all 0.2s ease;
  position: relative;
}
.heatmap-cell:hover { transform: scale(1.1); z-index: 10; box-shadow: 0 4px 12px rgba(0,0,0,0.2); }
.heatmap-cell.is-empty { background-color: #f0f0f0; opacity: 0.5; }
.cell-value { font-size: 8px; font-weight: bold; color: #333; text-shadow: 0 0 2px white; }
.heatmap-legend { text-align: center; }
.legend-title { font-weight: bold; margin-bottom: 6px; color: #333; }
.legend-gradient { display: inline-block; width: 240px; }
.legend-labels { display: flex; justify-content: space-between; margin-bottom: 4px; font-size: 12px; color: #666; }
.gradient-bar { height: 12px; background: linear-gradient(to right, #0000ff, #00ff00, #ffff00, #ff8000, #ff0000); border-radius: 4px; border: 1px solid #ddd; }
.empty-state { display: flex; flex-direction: column; align-items: center; justify-content: center; height: 100%; color: #666; }
</style>


